1use super::info::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::{decode_to_string, encode_string};
7use anyhow::Result;
8use overf::overflowing;
9
10#[derive(Debug)]
11pub struct CircusMesScriptBuilder {}
13
14impl CircusMesScriptBuilder {
15 pub const fn new() -> Self {
17 CircusMesScriptBuilder {}
18 }
19}
20
21impl ScriptBuilder for CircusMesScriptBuilder {
22 fn default_encoding(&self) -> Encoding {
23 Encoding::Cp932
24 }
25
26 fn build_script(
27 &self,
28 buf: Vec<u8>,
29 _filename: &str,
30 encoding: Encoding,
31 _archive_encoding: Encoding,
32 config: &ExtraConfig,
33 _archive: Option<&Box<dyn Script>>,
34 ) -> Result<Box<dyn Script + Send + Sync>> {
35 Ok(Box::new(CircusMesScript::new(buf, encoding, config)?))
36 }
37
38 fn extensions(&self) -> &'static [&'static str] {
39 &["mes"]
40 }
41
42 fn script_type(&self) -> &'static ScriptType {
43 &ScriptType::Circus
44 }
45
46 fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
47 try_parse_header(MemReaderRef::new(&buf[..buf_len])).ok()
48 }
49}
50
51fn try_parse_header(mut data: MemReaderRef<'_>) -> Result<u8> {
52 let head0 = data.read_i32()?;
53 let head1 = data.read_i32()?;
54 if head1 == 0x3 {
55 let offset = overflowing!(head0 as u64 * 0x6 + 0x4);
56 let version = data.peek_u16_at(offset)?;
57 if ScriptInfo::query_by_version(version).is_some() {
58 return Ok(10);
59 }
60 } else {
61 let offset = overflowing!(head0 as u64 * 0x4 + 0x4);
62 let version = data.peek_u16_at(offset)?;
63 if ScriptInfo::query_by_version(version).is_some() {
64 return Ok(10);
65 }
66 }
67 Err(anyhow::anyhow!("Not a Circus MES script"))
68}
69
70#[derive(Debug)]
71struct Token {
72 offset: usize,
73 length: usize,
74 value: u8,
75}
76
77pub struct CircusMesScript {
79 data: Vec<u8>,
80 encoding: Encoding,
81 is_new_ver: bool,
82 version: u16,
83 info: &'static ScriptInfo,
84 asm_bin_offset: usize,
85 blocks_offset: usize,
86 tokens: Vec<Token>,
87}
88
89impl CircusMesScript {
90 pub fn new(data: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
96 let head0 = i32::from_le_bytes(data[0..4].try_into()?);
97 let head1 = i32::from_le_bytes(data[4..8].try_into()?);
98 let mut is_new_ver = false;
99 let mut version = 0;
100 let mut info = config
101 .circus_mes_type
102 .as_ref()
103 .and_then(|name| ScriptInfo::query(name.as_ref()));
104 let mut asm_bin_offset = 0;
105 let mut blocks_offset = 0;
106 if head1 == 0x3 {
107 let offset = head0 * 0x6 + 0x4;
108 if data.len() > offset as usize {
109 if data.len() > offset as usize + 3 {
110 version =
111 u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
112 if info.is_none() {
113 info = ScriptInfo::query_by_version(version);
114 }
115 asm_bin_offset = offset as usize + 3;
116 }
117 blocks_offset = 8;
118 }
119 is_new_ver = true;
120 } else {
121 let offset = head0 * 0x4 + 0x4;
122 if data.len() > offset as usize {
123 if data.len() > offset as usize + 2 {
124 version =
125 u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
126 if info.is_none() {
127 info = ScriptInfo::query_by_version(version);
128 }
129 asm_bin_offset = offset as usize + 2;
130 }
131 blocks_offset = 4;
132 }
133 }
134 let info = info.ok_or(anyhow::anyhow!("Failed to detect version."))?;
135 let mut tokens = Vec::new();
136 let mut offset = 0;
137 let asm_bin_size = if asm_bin_offset == 0 {
138 0
139 } else {
140 data.len() - asm_bin_offset
141 };
142 while offset < asm_bin_size {
143 let value = data[asm_bin_offset + offset];
144 let length = if info.uint8x2.its(value) {
145 0x03
146 } else if info.uint8str.its(value) {
147 let mut len = 0x3;
148 let mut temp = data[asm_bin_offset + offset + len - 1];
149 while temp != 0x00 {
150 len += 0x1;
151 if asm_bin_offset + offset + len >= data.len() {
152 break;
153 }
154 temp = data[asm_bin_offset + offset + len - 1];
155 }
156 len
157 } else if info.string.its(value) || info.encstr.its(value) {
158 let mut len = 1;
159 let mut temp = data[asm_bin_offset + offset + len - 1];
160 while temp != 0x00 {
161 len += 0x1;
162 if asm_bin_offset + offset + len >= data.len() {
163 break;
164 }
165 temp = data[asm_bin_offset + offset + len - 1];
166 }
167 len
168 } else if info.uint16x4.its(value) {
169 0x09
170 } else {
171 return Err(anyhow::anyhow!(format!(
172 "Unknown token type: 0x{:02X} at offset {}",
173 value,
174 asm_bin_offset + offset
175 )));
176 };
177 let token = Token {
178 offset,
179 length,
180 value,
181 };
182 offset += length;
183 tokens.push(token);
184 }
185 Ok(CircusMesScript {
186 data,
187 encoding,
188 is_new_ver,
189 version,
190 info,
191 asm_bin_offset,
192 blocks_offset,
193 tokens,
194 })
195 }
196}
197
198impl std::fmt::Debug for CircusMesScript {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 f.debug_struct("CircusMesScript")
201 .field("encoding", &self.encoding)
202 .field("is_new_ver", &self.is_new_ver)
203 .field("version", &self.version)
204 .field("info", &self.info)
205 .field("asm_bin_offset", &self.asm_bin_offset)
206 .field("blocks_offset", &self.blocks_offset)
207 .field("tokens", &self.tokens)
208 .finish_non_exhaustive()
209 }
210}
211
212impl Script for CircusMesScript {
213 fn default_output_script_type(&self) -> OutputScriptType {
214 OutputScriptType::Json
215 }
216
217 fn default_format_type(&self) -> FormatOptions {
218 FormatOptions::Fixed {
219 length: 32,
220 keep_original: false,
221 break_words: false,
222 insert_fullwidth_space_at_line_start: true,
223 break_with_sentence: true,
224 #[cfg(feature = "jieba")]
225 break_chinese_words: true,
226 #[cfg(feature = "jieba")]
227 jieba_dict: None,
228 no_remove_space_at_line_start: false,
229 }
230 }
231
232 fn extract_messages(&self) -> Result<Vec<Message>> {
233 let mut mes = vec![];
234 let mut name = None;
235 for token in self.tokens.iter() {
236 let mut t = None;
237 if self.info.encstr.its(token.value) {
238 let mut text = self.data[self.asm_bin_offset + token.offset + 1
239 ..self.asm_bin_offset + token.offset + token.length - 1]
240 .to_vec();
241 for t in text.iter_mut() {
242 *t = (*t).overflowing_add(self.info.deckey).0;
243 }
244 t = Some(decode_to_string(self.encoding, &text, true)?);
245 } else if token.value == self.info.optunenc {
247 let text = &self.data[self.asm_bin_offset + token.offset + 1
248 ..self.asm_bin_offset + token.offset + token.length - 1];
249 t = Some(decode_to_string(self.encoding, text, true)?);
250 }
252 match t {
253 Some(t) => {
254 if token.value == self.info.nameopcode {
255 name = Some(t);
256 } else {
257 let message = Message::new(t, name.take());
258 mes.push(message);
259 }
260 }
261 None => {}
262 }
263 }
264 Ok(mes)
265 }
266
267 fn import_messages<'a>(
268 &'a self,
269 messages: Vec<Message>,
270 writer: Box<dyn WriteSeek + 'a>,
271 _filename: &str,
272 encoding: Encoding,
273 replacement: Option<&'a ReplacementTable>,
274 ) -> Result<()> {
275 let mut repls = Vec::new();
276 if !encoding.is_jis() {
277 fn insert_repl(
278 repls: &mut Vec<(String, String)>,
279 s: &'static str,
280 encoding: Encoding,
281 ) -> Result<()> {
282 let jis = encode_string(Encoding::Cp932, s, true)?;
283 let out = decode_to_string(encoding, &jis, true)?;
284 repls.push((s.to_string(), out));
285 Ok(())
286 }
287 let _ = insert_repl(&mut repls, "{", encoding);
288 let _ = insert_repl(&mut repls, "/", encoding);
289 let _ = insert_repl(&mut repls, "}", encoding);
290 if repls.len() < 3 {
291 eprintln!(
292 "Warning: Some replacements cannot used in current encoding. Ruby text may be broken."
293 );
294 crate::COUNTER.inc_warning();
295 }
296 }
297 if let Some(repl) = replacement {
298 for (k, v) in repl.map.iter() {
299 repls.push((k.to_string(), v.to_string()));
300 }
301 }
302
303 let source = MemReaderRef::new(&self.data);
304 let mut patcher = BinaryPatcher::new(source, writer, |pos| Ok(pos), |pos| Ok(pos))?;
305
306 let mut pending_messages: Vec<Message> = messages.into_iter().rev().collect();
307 let mut current_message = pending_messages.pop();
308 let mut block_updates: Vec<(u64, u32)> = Vec::new();
309 let mut block_index = 0usize;
310
311 for token in &self.tokens {
312 let token_start = (self.asm_bin_offset + token.offset) as u64;
313 patcher.copy_up_to(token_start)?;
314
315 if !self.is_new_ver {
316 let block_offset = (self.blocks_offset + block_index * 4) as u64;
317 let new_offset = patcher.map_offset(token_start)?;
318 let offset_value = (new_offset - self.asm_bin_offset as u64 + 2) as u32;
319 block_updates.push((block_offset, offset_value));
320 block_index += 1;
321 }
322
323 if self.info.is_message_opcode(token.value) {
324 if current_message.is_none() {
325 current_message = pending_messages.pop();
326 if current_message.is_none() {
327 return Err(anyhow::anyhow!("No more messages to import"));
328 }
329 }
330
331 let mut text = {
332 let message = current_message.as_mut().unwrap();
333 if self.info.is_name_opcode(token.value) {
334 match message.name.take() {
335 Some(name) => name,
336 None => {
337 let msg = message.message.clone();
338 current_message = None;
339 msg
340 }
341 }
342 } else {
343 let msg = message.message.clone();
344 current_message = None;
345 msg
346 }
347 };
348
349 for (from, to) in &repls {
350 text = text.replace(from, to);
351 }
352
353 let mut token_bytes = Vec::with_capacity(text.len() + 2);
354 token_bytes.push(token.value);
355 let mut encoded = encode_string(encoding, &text, false)?;
356 if self.info.is_encrypted_message(token.value) {
357 if encoded.contains(&self.info.deckey) {
358 eprintln!(
359 "Warning: text contains deckey 0x{:02X}, text may be truncated: {}",
360 self.info.deckey, text,
361 );
362 crate::COUNTER.inc_warning();
363 }
364 for b in &mut encoded {
365 *b = (*b).overflowing_sub(self.info.deckey).0;
366 }
367 }
368 token_bytes.extend_from_slice(&encoded);
369 token_bytes.push(0x00);
370 patcher.replace_bytes(token.length as u64, &token_bytes)?;
371 continue;
372 }
373
374 if self.is_new_ver && (token.value == 0x03 || token.value == 0x04) {
375 let block_offset = (self.blocks_offset + block_index * 4) as u64;
376 let original_block = patcher.input.cpeek_u32_at(block_offset)?;
377 let new_offset = patcher.map_offset(token_start)?;
378 let offset = (new_offset - self.asm_bin_offset as u64 + token.length as u64) as u32;
379 let value = (original_block & (0xFF << 0x18)) | offset;
380 block_updates.push((block_offset, value));
381 block_index += 1;
382 }
383
384 let token_end = token_start + token.length as u64;
385 patcher.copy_up_to(token_end)?;
386 }
387
388 patcher.copy_up_to(self.data.len() as u64)?;
389
390 for (offset, value) in block_updates {
391 patcher.patch_u32(offset, value)?;
392 }
393
394 Ok(())
395 }
396}